/**
  *@authors	Cameron Stewart, Tyler Williams, Fatimah Alowa, 
  *		Marlon Boothe,    Lauren Walker
  *@version	Group 2 Project 3 part 2
  *@since	2012-10-24
  */
  
#include <iostream>
#include "Tokenizer.h"
#include "sstream"
#include <cstring>

typedef struct
{
    std::string     str;
    TokenCode       tCode;
}
ReservedWord;

const ReservedWord resWordsLengthTwo[] =
{
    {"do", DO}, {"if", IF}, {"in", IN},
    {"of", OF}, {"or", OR}, {"to", TO}, {"", NO_TOKEN}
};
const ReservedWord resWordsLengthThree[] =
{
    {"and", AND}, {"div", DIV}, {"end", END}, {"for", FOR},
    {"mod", MOD}, {"nil", NIL}, {"not", NOT}, {"set", SET},
    {"var", VAR}, {"", NO_TOKEN}
};
const ReservedWord resWordsLengthFour[] =
{
    {"case", CASE}, {"else", ELSE}, {"file", FFILE},
    {"goto", GOTO}, {"then", THEN}, {"type", TYPE},
    {"with", WITH}, {"", NO_TOKEN}
};
const ReservedWord resWordsLengthFive[] =
{
    {"array", ARRAY}, {"begin", BEGIN}, {"const", CONST},
    {"label", LABEL}, {"until", UNTIL}, {"while", WHILE}, {"", NO_TOKEN}
};
const ReservedWord resWordsLengthSix[] =
{
    {"downto", DOWNTO}, {"packed", PACKED},
    {"record", RECORD}, {"repeat", REPEAT}, {"", NO_TOKEN}
};
const ReservedWord resWordsLengthSeven[] = {{"program", PROGRAM}, {"", NO_TOKEN}};
const ReservedWord resWordsLengthEight[] = {{"function", FUNCTION}, {"", NO_TOKEN}};
const ReservedWord resWordsLengthNine[] = {{"procedure", PROCEDURE}, {"", NO_TOKEN}};

const ReservedWord *reservedWordTable[] =
{
    NULL, NULL, resWordsLengthTwo, resWordsLengthThree,
    resWordsLengthFour, resWordsLengthFive, resWordsLengthSix,
    resWordsLengthSeven, resWordsLengthEight, resWordsLengthNine,
};

/**
	  *Constructor-initializes scanner and TokenCode object
	  *
	  *@param &scan	Scanner object address
	*/

Tokenizer::Tokenizer(Scanner &scan){
    scanner=&scan;
    code=NO_TOKEN;
}
Tokenizer::~Tokenizer(){
	//destructor
}

/** first skips past any blanks(the character "'"), then with
  * the first character in the token it decides to GetWord()
  * (if it is a letter), GetNumber()(if it is a number),
  * GetString()(if it is a quote â€˜), if it is the end of the file,
  * or else it must have a special so it can GetSpecial().
	*
	* @param charTable	type Charcode
	*
	* @return void
  */

void Tokenizer::GetToken(CharCode charTable[]){
    tokenString="";
    char ch = SkipBlanks();
    CharCode chType = charTable[ch];
    if(chType==QUOTE){
        GetString(ch);
    }else if(chType==DIGIT){
        GetNumber(ch,charTable);
    }else if(chType==LETTER){
        GetWord(ch,charTable);
    }else if(chType==EOF_CODE){
        code=END_OF_FILE;
    }else{
        GetSpecial(ch);
    }
}

/**
	*gets the token
	*
	*@return TokenCode
	*/ 

TokenCode Tokenizer::GetTokenCode(){
    return code;
}

/**
	* gets the string
	*
	* @return string
	*/

string Tokenizer::GetTokenString(){
    return tokenString;
}

/**
	  *gets the literal
	  *
	  *@return string
	  */ 

Literal *Tokenizer::GetLiteral(){
    return lit;
}

/**
	  *this method skips the white space in the code.
	  *this includes both tabs and newline charcters.
	  *
	  *@return char
	  */

char Tokenizer::SkipBlanks(){
    char ch = scanner->GetCharacter();
    while(ch==' ' || ch=='\t' || ch=='\n'){
        ch = scanner->GetCharacter();
    }
    return ch;
}

  /** finds a keyword or identifier in pascal.
    * A keyword can have letters or numbers in them.  Once it finds the
	  * end of a keyword or identifier, it backs up the character and
	  * makes the word all lowercase.  It then checks is this word a
	  * reserved word?  You can use the arrays provided in the source
	  * code to find out if something is a reserved word or not.
	  * If not then this token is an identifier.
	  *
	  *@param ch		character
	  *@param charTable[]	charTable to compare
	  *
	  *@return void
	  */

void Tokenizer::GetWord(char ch, CharCode charTable[]){
    string word="";
    while(charTable[ch]==LETTER || charTable[ch]==DIGIT){
        word+=ch;
        ch=scanner->GetCharacter();
    }
    scanner->BackUpCharacter();
    for(int i=0; i<word.length(); i++){
        word[i]=tolower(word[i]);
    }
    if(!IsReservedWord(word)){
        code=IDENTIFIER;
        tokenString=word;
    }
}

  /** finds two types of numbers;
    * integers and real numbers.  If it sees an integer it creates a new
	  * literal and stores the proper type and data in that literal.  A real
	  * number is any number that has the character period â€˜.â€™ in it and/or
	  * the character â€˜eâ€™ in it.  After the â€˜eâ€™ there can be a negative sign,
	  * and the â€˜eâ€™ stands for exponent.  If GetNumber() sees a real
	  * number it creates a new literal and stores the data and type in
	  * that literal.  Finally, the GetNumber() method stores the type of
	  * token it has. 
	  *
	  *@param ch		character
	  *@param charTable[]	charTable to compare
	  *
	  *@return void
    */

void Tokenizer::GetNumber(char ch, CharCode codes[]){
    bool int_num=true;
    code=NUMBER;
    string num;
    while(isdigit(ch) || ch=='.' || ch=='e' || ch=='-'){
        if(ch=='.' || ch=='e'){
            int_num=false;
        }
        num+=ch;
        ch=scanner->GetCharacter();
    }
    scanner->BackUpCharacter();
    stringstream myStream(num);
    if(int_num){
        int intNum;
        myStream >> intNum;
        lit=new Literal(INTEGER_LIT);
        lit->SetValue(intNum);
    }else{
        float flNum;
        myStream >> flNum;
        lit=new Literal(REAL_LIT);
        lit->SetValue(flNum);
    }
    code=NUMBER;
}

/** finds a string token.  A string is
  * between a single quote and another single quote.  The
  * GetString() method creates a new literal object and stores the
  * proper literal type and data.  Finally the GetString() method sets
  * the token to the correct token code type
	* @param ch 	character 
	*
	* @return 	void
  */

void Tokenizer::GetString(char ch){
    ch=scanner->GetCharacter();
    while(ch!='\'' || ((int)ch)!=39){           
        tokenString+=ch;
        ch=scanner->GetCharacter();
    }
    lit=new Literal(STRING_LIT);
    lit->SetValue(tokenString);
    code=STRING;
}

 /**
	  * finds a special character in Pascal.
    * If you would like to see a list of these special characters, you can
    * look at the symbol_strings array in the Print.cpp file.  The fifth
    * through the twenty fifth elements are all the special characters
    * GetSpecial() looks for.  Finally, GetSpecial() will set tokenString
    * with the special characters and set the â€œcodeâ€ to the correct type.
	  *
	  *@param ch 	character 
	  *
	  *@return 	void
    */

void Tokenizer::GetSpecial(char ch){
    string temp="";
    temp+=ch;
    string specials[]={ "^","*","(",")","-","+","=","[","]",":",";","<",">",",",".","/",":=","<=",">=","<>",".."};
    TokenCode specialsCodes[]={UPARROW, STAR, LPAREN, RPAREN, MINUS, PLUS, EQUAL, LBRACKET,RBRACKET,
        COLON, SEMICOLON, LT, GT, COMMA, PERIOD, SLASH,COLONEQUAL, LE, GE, NE, DOTDOT};
    ch=scanner->GetCharacter();
    if(!ispunct(ch) || (ch=='\'' || ((int)ch)==39) || ch==';'){
        scanner->BackUpCharacter();
    }else{
        temp+=ch;
    }
    
    for(int i=0; i<sizeof(specials)/sizeof(specials[0]); i++){
        if(temp==specials[i]){
            tokenString=specials[i];
            code=specialsCodes[i];
            i=sizeof(specials)/sizeof(specials[0]);
        }
    }    
}

 /**
	  *Check if word is a reserved word
	  *
	  *@param &word		reference to string object
	  *
	  *@return bool		true/false if matches Pascal reserved word
	  */

bool Tokenizer::IsReservedWord(string &word){ 
    for(const ReservedWord *array = reservedWordTable[word.length()]; strcmp(array->str.c_str(),"")!=0; array++){
        if(word==array->str){
            code=array->tCode;
            tokenString=array->str;
            return true;
        }
    }
    return false;
}